#pragma once

#include "cocos2d.h"
#include <string>
#include <memory>
#include <atomic>
#include <thread>
#include <cstdlib>

namespace elloop {

	class DownloadTask;
	typedef std::shared_ptr<DownloadTask> TaskPtr;

	class MultiDownloader {
	public:

		typedef std::deque<TaskPtr> TaskList;


		// not thread-safe.
		static MultiDownloader* getInstance() {
			if (nullptr == _instance) {
				_instance = new MultiDownloader;
			}
			return _instance;
		}

		void pushTask(TaskPtr task);
		void pushTask(const std::string & url, const std::string& saveFileName);
		void startTasks();

	protected:

		MultiDownloader();
		~MultiDownloader();

		// thread work function.
		void downloadThreadFunc(TaskPtr pTask);

	private:
		static MultiDownloader * _instance;

		TaskList _taskList;
		std::mutex _taskListMutex;

		TaskList _finishedList;
		std::mutex _finishedListMutex;

		TaskList _failedList;
		std::mutex _failedListMutex;

		bool _isCurlGlobalInited;
	};

	class DownloadTask {
	public:
		DownloadTask(const std::string & url, const std::string& tag = "");

		~DownloadTask();

		// forbidden copy and assignment.
		DownloadTask(const DownloadTask& other) = delete;
		DownloadTask(DownloadTask&& other) = delete;
		DownloadTask& operator()(const DownloadTask& other) = delete;

		size_t writeFunction(char *content, size_t size, size_t nmemb, void *userData);

		int progressFunction(void * clientPtr, double downloadTotal, double downloaded, double upTotal, double upLoaded);

		void onFileDownloaded();

		typedef std::function<int(void*, double, double, double, double)> ProgressFunction;
		typedef std::function<size_t(char*, size_t, size_t, void*)> WriteFunction;
		typedef std::function<void(unsigned char *, size_t)> SaveDataFunction;

	private:
		std::string _tag;
		std::string _url;
		unsigned char * _data;
		size_t _size;
		std::atomic<double> _totalSize;
		std::atomic<double> _downloadedSize;

		ProgressFunction _progressFunction;
		WriteFunction _writeFunction;
		SaveDataFunction _saveDataFunction;
		std::function<void(double, double)> _downloadProgressCallback;
	public:

		/************************************************************************/
		/* getters and setters */
		/************************************************************************/
		// all are non-const version.

		void setProgressFunction(ProgressFunction progressFunc) {
			_progressFunction = progressFunc;
		}

		DownloadTask::ProgressFunction progressFunction() { return _progressFunction; }

		void setWriteFunction(WriteFunction writeFunction) {
			_writeFunction = writeFunction;
		}
		DownloadTask::WriteFunction writeFunction() { return _writeFunction; }


		void setSaveDataFunction(SaveDataFunction saveFunc) {
			_saveDataFunction = saveFunc;
		}
		DownloadTask::SaveDataFunction saveDataFunction() { return _saveDataFunction; }

		void setProgressCallback(decltype(_downloadProgressCallback) callback) {
			_downloadProgressCallback = callback;
		}
		decltype(_downloadProgressCallback) DownloadProgressCallback() { return _downloadProgressCallback; }

		const std::string & tag() { return _tag; }
		void setTag(std::string val) { _tag = val; }

		const std::string & url() { return _url; }
		void setUrl(const std::string & val) { _url = val; }

		unsigned char * data() { return _data; }

		size_t size() { return _size; }

		double totalSize() {
			return _totalSize.load();
		}

		double downloadedSize() {
			return _downloadedSize.load();
		}

	};
}
